package com.rxt.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.rxt.common.constant.RedisConstants;
import com.rxt.common.constant.RegexConstants;
import com.rxt.common.constant.SystemConstants;
import com.rxt.common.exception.CustomException;
import com.rxt.common.result.Result;
import com.rxt.common.result.ResultCodeEnum;
import com.rxt.model.domain.UUser;
import com.rxt.mapper.UUserMapper;
import com.rxt.model.dto.UUserDto;
import com.rxt.model.vo.LoginVo;
import com.rxt.model.vo.PasswordVo;
import com.rxt.service.IUUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rxt.util.BaseContext;
import com.rxt.util.JwtUtils;
import com.rxt.util.PwdUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 周梓文
 * @since 2023-08-24
 */
@Slf4j
@Service
public class UUserServiceImpl extends ServiceImpl<UUserMapper, UUser> implements IUUserService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 登录
     */
    @Override
    public Result login(LoginVo loginVo) {
        if (loginVo == null || loginVo.getType() == null) {
            throw new CustomException(ResultCodeEnum.INVALID_PARAMETER);
        }
        // 判断登录方式
        int type = loginVo.getType();
        if (SystemConstants.LOGIN_BY_CAPTCHA == type) { // 验证码登录
            return loginByCaptcha(loginVo);
        } else if (SystemConstants.LOGIN_BY_PWD == type) { // 密码登录
            return loginByPwd(loginVo);
        }
        throw new CustomException(ResultCodeEnum.INVALID_PARAMETER);
    }

    /**
     * 密码登录
     */
    private Result loginByPwd(LoginVo loginVo) {
        String phone = loginVo.getPhone();
        String password = loginVo.getPassword();
        if (StrUtil.isEmptyIfStr(phone) || StrUtil.isEmptyIfStr(password)) {
            throw new CustomException(ResultCodeEnum.INVALID_PARAMETER);
        }
        // 查询用户
        UUser user = getOne(new LambdaQueryWrapper<UUser>()
                .eq(UUser::getPhone, phone));
        // 用户不存在
        if (user == null) {
            return Result.fail("用户不存在");
        }
        // 用户禁用
        if (user.getDeleteStatus() == 1) {
            return Result.fail("用户已被禁用");
        }
        // 密码校验
        if (!PwdUtils.check(password, user.getSalt(), user.getPassword())) {
            return Result.fail("密码错误");
        }
        // 登录成功
        Map<String, String> map = new HashMap<>();
        map.put("token", JwtUtils.createToken(user.getId()));
        user2Cache(user);
        return Result.ok(map);
    }

    /**
     * 验证码登录
     */
    private Result loginByCaptcha(LoginVo loginVo) {
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        if (StrUtil.isEmptyIfStr(phone) || StrUtil.isEmptyIfStr(code)) {
            throw new CustomException(ResultCodeEnum.INVALID_PARAMETER);
        }
        // 从redis取出验证码
        String codeKey = RedisConstants.CAPTCHA_KEY + phone;
        String cacheCode = stringRedisTemplate.opsForValue().get(codeKey);
        // 验证码校验
        if (cacheCode == null || !Objects.equals(code, cacheCode)) {
            return Result.fail("验证码错误");
        }
        // 从redis删除验证码
        stringRedisTemplate.delete(codeKey);
        // 查询用户
        UUser user = getOne(new LambdaQueryWrapper<UUser>()
                .eq(UUser::getPhone, phone));
        Map<String, String> map = new HashMap<>();
        // 用户不存在（首次登录，自动注册）
        boolean registerFlag = false;
        if (user == null) {
            registerFlag = true;
            user = register(phone);
        }
        // 用户存在
        // 判断用户是否被禁用
        if (!registerFlag && user.getDeleteStatus() == 1) {
            return Result.fail("用户已被禁用");
        }
        map.put("token", JwtUtils.createToken(user.getId()));
        // 将用户信息存入redis
        user2Cache(user);
        return Result.ok(map);
    }

    /**
     * 用户注册
     */
    private UUser register(String phone) {
        UUser user = new UUser();
        user.setUsername(SystemConstants.USERNAME_PREFIX + phone); // 设置用户名
        user.setRole(SystemConstants.SYSTEM_ROLE_USER); // 设置用户身份
        user.setPhone(phone); // 设置手机号
        // 生成密码盐
        String salt = PwdUtils.getRandomSalt();
        // 默认密码加密
        String password = PwdUtils.encrypt(SystemConstants.DEFAULT_PASSWORD, salt);
        user.setPassword(password); // 设置密码
        user.setSalt(salt); // 设置加密盐
        user.setLastLogin(LocalDateTime.now());

        save(user);
        return user;
    }

    /**
     * 将用户信息放入缓存
     */
    private void user2Cache(UUser user) {
        UUserDto userDto = BeanUtil.copyProperties(user, UUserDto.class);
        String userDtoJson = JSON.toJSONString(userDto);
        stringRedisTemplate.opsForValue().set(
                RedisConstants.USER_KEY + user.getId(), userDtoJson,
                RedisConstants.USER_KEY_TTL, TimeUnit.MINUTES);
    }

    private void user2Cache(UUserDto userDto) {
        String userDtoJson = JSON.toJSONString(userDto);
        stringRedisTemplate.opsForValue().set(
                RedisConstants.USER_KEY + userDto.getId(), userDtoJson,
                RedisConstants.USER_KEY_TTL, TimeUnit.MINUTES);
    }

    /**
     * 获取验证码
     */
    @Override
    public Result getCaptcha(String phone) {
        // 校验手机号
        if (StrUtil.isEmptyIfStr(phone) || !ReUtil.isMatch(RegexConstants.PHONE_REGEX, phone)) {
            return Result.fail("手机号错误");
        }
        // 生成6位数的验证码
        String code = RandomUtil.randomNumbers(6);
        // 将生成的验证码存入redis中，保存5分钟
        stringRedisTemplate.opsForValue().set(
                RedisConstants.CAPTCHA_KEY + phone, code, RedisConstants.CAPTCHA_KEY_TTL, TimeUnit.MINUTES);
        // TODO: 发送验证码短信
        log.info("手机号：{}，验证码：{}", phone, code);
        return Result.ok("验证码已发送");
    }

    /**
     * 获取当前用户信息
     */
    @Override
    public Result currentUser() {
        return Result.ok(BaseContext.get());
    }

    /**
     * 忘记密码
     */
    @Override
    public Result forgetPwd(LoginVo loginVo, HttpSession session) {
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();
        if (StrUtil.isEmptyIfStr(phone) || StrUtil.isEmptyIfStr(code)) {
            return Result.fail(ResultCodeEnum.INVALID_PARAMETER);
        }
        if (getOne(new LambdaQueryWrapper<UUser>().eq(UUser::getPhone, phone)) == null) {
            return Result.fail("用户不存在");
        }
        // 从redis中取出验证码
        String codeKey = RedisConstants.CAPTCHA_KEY + phone;
        String cacheCode = stringRedisTemplate.opsForValue().get(codeKey);
        // 验证码校验
        if (cacheCode == null || !Objects.equals(code, cacheCode)) {
            return Result.fail("验证码错误");
        }
        // 从redis删除验证码
        stringRedisTemplate.delete(codeKey);
        session.setAttribute("isVerify", phone);
        return Result.ok();
    }

    /**
     * 重置密码
     */
    @Override
    public Result resetPwd(PasswordVo pwdVo, HttpSession session) {
        Integer type = pwdVo.getType();
        if (type == 0) { // 未登录情况
            String phone = session.getAttribute("isVerify").toString();
            if (phone == null) { // 之前未进行验证
                return Result.fail(ResultCodeEnum.INVALID_PARAMETER);
            }
            if (updateUserPwd(phone, pwdVo.getNewPassword())) {
                return Result.ok("密码已重置");
            }
            return Result.fail("用户不存在");
        } else if (type == 1) { // 登录的情况下
            String phone = BaseContext.get().getPhone();
            // 验证原密码是否正确
            if (isVerifyForPwd(phone, pwdVo.getPassword())) {
                updateUserPwd(phone, pwdVo.getNewPassword());
                session.removeAttribute("isVerify");
                return Result.ok("密码已重置");
            } else {
                return Result.fail("原密码错误");
            }
        }
        return Result.fail();
    }

    /**
     * 退出登录
     */
    @Override
    public Result logout() {
        // 删除redis中的用户信息
        stringRedisTemplate.delete(RedisConstants.USER_KEY + BaseContext.get().getId());
        return Result.ok();
    }

    /**
     * 更新用户个人信息
     */
    @Override
    public Result updateUserInfo(UUserDto userDto) {
        UUser tmp = BeanUtil.copyProperties(userDto, UUser.class);
        System.out.println(userDto);
        System.out.println(tmp);
        if (!update(tmp, new LambdaUpdateWrapper<UUser>().eq(UUser::getId, BaseContext.get().getId()))) {
            return Result.fail();
        }
        UUserDto cur = BaseContext.get();
        if (!StrUtil.isEmptyIfStr(userDto.getAddress())) cur.setAddress(userDto.getAddress());
        if (!StrUtil.isEmptyIfStr(userDto.getEmail())) cur.setEmail(userDto.getEmail());
        if (!StrUtil.isEmptyIfStr(userDto.getIdentityNum())) cur.setIdentityNum(userDto.getIdentityNum());
        if (!StrUtil.isEmptyIfStr(userDto.getRealName())) cur.setRealName(userDto.getRealName());
        // 更新Redis中的数据
        user2Cache(cur);
        return Result.ok();
    }

    /**
     * 校验密码
     */
    public boolean isVerifyForPwd(String phone, String password) {
        UUser user = getOne(new LambdaQueryWrapper<UUser>().eq(UUser::getPhone, phone));
        if (user != null) {
            return PwdUtils.check(password, user.getSalt(), user.getPassword());
        }
        return false;
    }

    /**
     * 修改用户密码
     */
    public boolean updateUserPwd(String phone, String newPwd) {
        // 查询用户密码盐
        UUser user = getOne(new LambdaQueryWrapper<UUser>().eq(UUser::getPhone, phone));
        if (user != null) {
            UUser tmp = new UUser();
            tmp.setPassword(PwdUtils.encrypt(newPwd, user.getSalt()));
            return update(tmp, new LambdaQueryWrapper<UUser>().eq(UUser::getPhone, phone));
        }
        return false;
    }
}
